/****************************************************************************
*
*                            Open Watcom Project
*
*    Portions Copyright (c) 1983-2002 Sybase, Inc. All Rights Reserved.
*
*  ========================================================================
*
*    This file contains Original Code and/or Modifications of Original
*    Code as defined in and that are subject to the Sybase Open Watcom
*    Public License version 1.0 (the 'License'). You may not use this file
*    except in compliance with the License. BY USING THIS FILE YOU AGREE TO
*    ALL TERMS AND CONDITIONS OF THE LICENSE. A copy of the License is
*    provided with the Original Code and Modifications, and is also
*    available at www.sybase.com/developer/opensource.
*
*    The Original Code and all software distributed under the License are
*    distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
*    EXPRESS OR IMPLIED, AND SYBASE AND ALL CONTRIBUTORS HEREBY DISCLAIM
*    ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF
*    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR
*    NON-INFRINGEMENT. Please see the License for the specific language
*    governing rights and limitations under the License.
*
*  ========================================================================
*
* Description:  Librarian symbol table processing.
*
****************************************************************************/


#include "wlib.h"

static sym_table        FileTable;
static sym_file         *CurrFile;
static sym_entry        **HashTable;
static sym_entry        **SortedSymbols;

static char             *padding_string;
static int              padding_string_len;

#define HASH_SIZE       256

static int Hash( char *string, unsigned *plen );

void InitFileTab( void )
/**********************/
{
	FileTable.first = NULL;
	FileTable.add_to = &FileTable.first;
	SortedSymbols = NULL;
	HashTable = MemAllocGlobal( HASH_SIZE * sizeof( HashTable[ 0 ] ) );
	memset( HashTable, 0, HASH_SIZE * sizeof( HashTable[ 0 ] ) );
}

static void FiniSymFile( sym_file *sfile )
/****************************************/
{
	sym_entry           *sym, *next_sym;
	elf_import_sym      *temp;

	for( sym = sfile->first; sym != NULL; sym = next_sym ) {
		next_sym = sym->next;
		MemFreeGlobal( sym );
	}
	MemFreeGlobal( sfile->full_name );
	MemFreeGlobal( sfile->arch.name );
	MemFreeGlobal( sfile->arch.ffname );
	if( sfile->import != NULL ) {
		switch( sfile->import->type ) {
		case ELF:
		case ELFRENAMED:
			for( temp = sfile->import->u.elf.symlist; temp != NULL;
						 temp = sfile->import->u.elf.symlist ) {
				sfile->import->u.elf.symlist = temp->next;
				MemFreeGlobal( temp->name );
				MemFreeGlobal( temp );
			}
			MemFreeGlobal( sfile->import->DLLName );
			break;
		default:
			MemFreeGlobal( sfile->import->DLLName );
			MemFreeGlobal( sfile->import->u.sym.symName );
			MemFreeGlobal( sfile->import->u.sym.exportedName );
			break;
		}
		MemFreeGlobal( sfile->import );
	}
	MemFreeGlobal( sfile );
}


void CleanFileTab( void )
/***********************/
{
	sym_file    *last = NULL;
	sym_file    *curr;
	sym_file    *next;

	for( curr = FileTable.first; curr; curr = next ) {
		next = curr->next;

		/*
		 * If curr->first is NULL then either this file contains no
		 * symbols or we are ignoring all of them.  Remove the file.
		 */
		if( !curr->first ) {
			if( last ) {
				last->next = curr->next;
			} else {
				FileTable.first = curr->next;
			}

			if( &(curr->next) == FileTable.add_to ) {
				if( last ) {
					FileTable.add_to = &(last->next);
				} else {
					FileTable.add_to = &(FileTable.first);
				}
			}

			FiniSymFile( curr );
		} else {
			last = curr;
		}
	}
}


void ResetFileTab( void )
/***********************/
{
	sym_file    *sfile;
	sym_file    *next_sfile;

	memset( HashTable, 0, HASH_SIZE * sizeof( HashTable[ 0 ] ) );
	for( sfile = FileTable.first; sfile != NULL; sfile = next_sfile ) {
		next_sfile = sfile->next;
		FiniSymFile( sfile );
	}
	FileTable.first = NULL;
	FileTable.add_to = &FileTable.first;
	MemFreeGlobal( SortedSymbols );
	SortedSymbols = NULL;
}

void FiniFileTab( void )
/***********************/
{
	MemFreeGlobal( HashTable );
}


static void RemoveFromHashTable( sym_entry *sym )
/***********************************************/
{
	sym_entry       *hash;
	sym_entry       *prev;
	int             hval;
	unsigned        len;

	hval = Hash( sym->name, &len );
	hash = HashTable[ hval ];

	if( hash == sym ) {
		HashTable[ hval ] = sym->hash;
	} else if( hash ) {
		prev = hash;

		for( hash = hash->hash; hash; hash = hash->hash ) {
			if( hash == sym ) {
				prev->hash = hash->hash;
				break;
			} else {
				prev = hash;
			}
		}
	}
}


static void NewSymFile( arch_header *arch )
/*****************************************/
{
	sym_file    *sfile;

	sfile = MemAllocGlobal( sizeof( sym_file ) );
	sfile->first = NULL;
	sfile->next = NULL;
	sfile->arch = *arch;
	sfile->import = NULL;
	sfile->inlib_offset = 0;
	sfile->full_name = DupStrGlobal( sfile->arch.name );
	sfile->arch.name = DupStrGlobal( sfile->arch.name );
	if( Options.trim_path )
		TrimPath( sfile->arch.name );
	sfile->name_length = strlen( sfile->arch.name );
	if( sfile->arch.ffname ) {
		sfile->arch.ffname = DupStrGlobal( sfile->arch.ffname );
		sfile->ffname_length = strlen( sfile->arch.ffname );
	} else {
		sfile->ffname_length = 0;
	}
	*(FileTable.add_to) = sfile;
	FileTable.add_to = &sfile->next;
	CurrFile = sfile;
}



static int CompSyms( const void *ap, const void *bp )
/***************************************************/
{
	const sym_entry *a = *(const sym_entry **)ap;
	const sym_entry *b = *(const sym_entry **)bp;
	return( strcmp( a->name, b->name ) );
}

static void WriteFileHeader( arch_header *arch )
/**********************************************/
{
	ar_header   ar;

	CreateARHeader( &ar, arch );
	WriteNew( &ar, AR_HEADER_SIZE );
}

static void WritePad( file_offset size )
/**************************************/
{
	if( NeedsRounding( size ) ) {
		WriteNew( padding_string, padding_string_len );
	}
}

static long             NumFiles;
static long             NumSymbols;
static file_offset      TotalNameLength;
static file_offset      TotalFFNameLength;
static file_offset      TotalSymbolLength;

static void SortSymbols( void )
/*****************************/
{
	sym_file    *sfile;
	sym_entry   *sym;
	sym_entry   **sym_curr;
	int         i;
	int         name_length = 0;

	NumFiles = 0;
	NumSymbols = 0;
	TotalNameLength = 0;
	TotalFFNameLength = 0;
	TotalSymbolLength = 0;
	for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
		++NumFiles;
		switch( Options.libtype ) {
		case WL_LTYPE_AR:
			// Always using "full" filename for AR
			if( sfile->arch.ffname ) {
				name_length = sfile->ffname_length;
			} else {
				sfile->ffname_length = 0;
				name_length = sfile->name_length;
			}
			if( Options.ar_libformat == AR_FMT_BSD ) {
				// BSD doesn't use special file name table
				sfile->name_offset = -1;
				name_length = 0;
			} else if( Options.ar_libformat == AR_FMT_GNU ) {
				if( name_length < AR_NAME_LEN ) {
					sfile->name_offset = -1;
					name_length = 0;
				} else {
					sfile->name_offset = TotalNameLength;
					name_length += AR_LONG_NAME_END_STRING_LEN;   // + "/\n"
				}
			} else {
				/* jwlink: don't store short names in the "longnames" member */
				if( name_length < AR_NAME_LEN ) {
					sfile->name_offset = -1;
					name_length = 0;
				} else {
					sfile->name_offset = TotalNameLength;
					name_length += 1;     // + "\n"
				}
			}
			break;
		case WL_LTYPE_MLIB:
			// If no full filename, assume name is full, and trim
			// it to get non-full filename.
			if( sfile->arch.ffname == NULL ) {
				sfile->arch.ffname = sfile->arch.name;
				sfile->ffname_length = strlen( sfile->arch.ffname );
				sfile->arch.name = MemAllocGlobal( _MAX_FNAME + _MAX_EXT + 1 );
				_splitpath( sfile->arch.ffname, NULL, NULL, sfile->arch.name, NULL );
				_splitpath( sfile->arch.ffname, NULL, NULL, NULL, sfile->arch.name + strlen( sfile->arch.name ) );
				sfile->name_length = strlen( sfile->arch.name );
			}
			name_length = sfile->name_length;
			sfile->name_offset = TotalNameLength;
			name_length += LIB_LONG_NAME_END_STRING_LEN;  // + "/\n"
			break;
		}
		TotalNameLength += name_length;
		TotalFFNameLength += sfile->ffname_length + 1;
		for( sym = sfile->first; sym != NULL; sym = sym->next ) {
			++NumSymbols;
			TotalSymbolLength += sym->len + 1;
		}
	}

	if( NumSymbols == 0 ) {
		SortedSymbols = NULL;
		Warning( ERR_NO_SYMBOLS );
	} else {
		SortedSymbols = MemAllocGlobal( NumSymbols * sizeof( SortedSymbols[ 0 ] ) );
	}

	sym_curr = SortedSymbols;
	for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
		for( sym = sfile->first; sym != NULL; sym = sym->next ) {
			*sym_curr = sym;
			++sym_curr;
		}
	}

	qsort( SortedSymbols, NumSymbols, sizeof(sym_entry *), CompSyms );

	// re-hook symbols onto files in sorted order

	for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
		sfile->first = NULL;
	}

	for( i = NumSymbols - 1; i >= 0; --i ) {
		sym = SortedSymbols[ i ];
		sym->next = sym->file->first;
		sym->file->first = sym;
	}
}

void WriteFile( sym_file *sfile )
/*******************************/
{
	libfile     io;

	if( sfile->import == NULL ) {
		if( sfile->inlib_offset == 0 ) {
			io = LibOpen( sfile->full_name, LIBOPEN_BINARY_READ );
		} else {
			io = InLibHandle( sfile->inlib );
			LibSeek( io, sfile->inlib_offset, SEEK_SET );
		}
		if( sfile->obj_type == WL_FTYPE_OMF ) {
			OmfCopy( io, sfile );
		} else {
			Copy( io, NewLibrary, sfile->arch.size );
		}
		if( sfile->inlib_offset == 0 ) {
			LibClose( io );
		}
	} else {
		switch( sfile->obj_type ) {
		case WL_FTYPE_ELF:
			ElfWriteImport( NewLibrary, sfile );
			break;
		case WL_FTYPE_COFF:
			CoffWriteImport( NewLibrary, sfile );
			break;
		case WL_FTYPE_OMF:
			OmfWriteImport( sfile );
			break;
		}
	}
}

static void WriteOmfLibTrailer( void )
{
	OmfRecord   *rec;
	unsigned    size;

	size = DIC_REC_SIZE - LibTell( NewLibrary ) % DIC_REC_SIZE;
	rec = MemAlloc( size );
	rec->basic.type = LIB_TRAILER_REC;
	rec->basic.len = GET_LE_16( size - 3 );
	memset( rec->basic.contents, 0, size - 3 );
	WriteNew( rec, size );
	MemFree( rec );
}

static void WriteOmfLibHeader( unsigned_32 dict_offset, unsigned_16 dict_size )
{
	OmfLibHeader    lib_header; // i didn't use omfRec because page size can be quite big

	LibSeek( NewLibrary, 0, SEEK_SET );
	lib_header.type = LIB_HEADER_REC;
	lib_header.page_size = GET_LE_16( Options.page_size - 3 );
	lib_header.dict_offset = GET_LE_32( dict_offset );
	lib_header.dict_size = GET_LE_16( dict_size );
	if( Options.respect_case ) {
		lib_header.flags = 1;
	} else {
		lib_header.flags = 0;
	}
	WriteNew( &lib_header, sizeof( lib_header ) );
}

static unsigned OptimalPageSize( void )
/*************************************/
{
	unsigned    i;
	sym_file    *sfile;
	file_offset offset;
	unsigned    page_size;

	for( i = 4; i < 16; i++ ) {
		page_size = 1 << i;
		offset = page_size;
		for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
			if( offset / page_size > (unsigned long)USHRT_MAX )
				break;
			offset += ( sfile->arch.size + page_size - 1 ) & ~( page_size - 1 );
		}
		if( sfile == NULL ) {
			break;
		}
	}
	return( page_size );
}

static void WriteOmfFileTable( void )
/***********************************/
{
	sym_file    *sfile;
	unsigned    num_blocks;
	unsigned    dict_offset;

	if( Options.page_size == 0 ) {
		Options.page_size = DEFAULT_PAGE_SIZE;
	} else if( Options.page_size == (unsigned short)-1 ) {
		Options.page_size = OptimalPageSize();
	}
	PadOmf( TRUE );

	for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
		WriteOmfFile( sfile );
	}
	WriteOmfLibTrailer();
	dict_offset = LibTell( NewLibrary );
	num_blocks = WriteOmfDict( FileTable.first );
	WriteOmfLibHeader( dict_offset, num_blocks );
}

static void WriteArMlibFileTable( void )
/**************************************/
{
	arch_header     arch;
	sym_file        *sfile;
	sym_entry       *sym;
	file_offset     dict1_size = 0;
	file_offset     dict2_size = 0;
	file_offset     header_size = 0;
	int             i;
	time_t          currenttime = time( NULL );
	file_offset     obj_offset;
	int             index;
	bool            isBSD;

	SortSymbols();
	DEBUG(("WriteArMlibFileTable, NumSymbols=%u NumFiles=%u TotalNameLength=%u\n", NumSymbols, NumFiles, TotalNameLength ));

	// figure out this dictionary sizes

	switch( Options.libtype ) {
	case WL_LTYPE_AR:
		dict1_size = ( NumSymbols + 1 ) * sizeof(unsigned_32)
					+ RoundWord( TotalSymbolLength );

		header_size = AR_IDENT_LEN
					+ AR_HEADER_SIZE + dict1_size;

		switch( Options.ar_libformat ) {
		case AR_FMT_BSD:
			dict2_size = 0;

			padding_string     = "\0";
			padding_string_len = 1;
			break;
		case AR_FMT_GNU:
			dict2_size = 0;

			if( TotalNameLength > 0 ) {
				header_size += AR_HEADER_SIZE + RoundWord( TotalNameLength );
			}

			padding_string     = "\0";
			padding_string_len = 1;
			break;
		default:
			dict2_size = ( NumFiles + 1 ) * sizeof( unsigned_32 )
						+ sizeof( unsigned_32 ) + NumSymbols * sizeof( unsigned_16 )
						+ RoundWord( TotalSymbolLength );

			header_size += AR_HEADER_SIZE + dict2_size;

			/* jwlink */
			//if( TotalNameLength > 0 ) {
			if( TotalNameLength > 0 || Options.ar_libformat == AR_FMT_COFF ) {
				header_size += AR_HEADER_SIZE + RoundWord( TotalNameLength );
			}

			padding_string     = AR_FILE_PADDING_STRING;
			padding_string_len = AR_FILE_PADDING_STRING_LEN;
			break;
		}
		break;
	case WL_LTYPE_MLIB:
		dict1_size = 0;

		dict2_size = ( NumSymbols + 1 ) * sizeof( unsigned_32 ) + NumSymbols
					+ TotalSymbolLength;

		header_size = LIBMAG_LEN + LIB_CLASS_LEN + LIB_DATA_LEN
					+ LIB_HEADER_SIZE + RoundWord( dict2_size )
					+ LIB_HEADER_SIZE + RoundWord( TotalNameLength )
					+ LIB_HEADER_SIZE + RoundWord( TotalFFNameLength );

		padding_string     = LIB_FILE_PADDING_STRING;
		padding_string_len = LIB_FILE_PADDING_STRING_LEN;
		break;
	}

	// calculate the object files offsets

	index = 0;
	obj_offset = 0;
	isBSD = ( ( Options.libtype == WL_LTYPE_AR ) && ( Options.ar_libformat == AR_FMT_BSD ) );
	for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
		sfile->new_offset = obj_offset + header_size;
		sfile->index = ++index;
		if( isBSD && ( sfile->name_length > AR_NAME_LEN || strchr( sfile->arch.name, ' ' ) != NULL ) ) {
			obj_offset += RoundWord( sfile->arch.size + sfile->name_length ) + AR_HEADER_SIZE;
		} else {
			obj_offset += RoundWord( sfile->arch.size ) + AR_HEADER_SIZE;
		}
	}

	switch( Options.libtype ) {
	case WL_LTYPE_AR:
		WriteNew( AR_IDENT, AR_IDENT_LEN );
		break;
	case WL_LTYPE_MLIB:
		WriteNew( LIBMAG, LIBMAG_LEN );
		WriteNew( LIB_CLASS_DATA_SHOULDBE, LIB_CLASS_LEN + LIB_DATA_LEN );
		break;
	}

	// write the useless dictionary

	arch.date = currenttime;
	arch.uid = 0;
	arch.gid = 0;
	arch.mode = 0;
	if( dict1_size > 0 ) {
		DEBUG(("WriteArMlibFileTable: writing 1. directory\n" ));
		arch.size = dict1_size;     // word round size
		arch.name = "/";
		WriteFileHeader( &arch );

		WriteBigEndian32( NumSymbols );
		for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
			for( sym = sfile->first; sym != NULL; sym = sym->next ) {
				WriteBigEndian32( sym->file->new_offset );
			}
		}
		for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
			for( sym = sfile->first; sym != NULL; sym = sym->next ) {
				WriteNew( sym->name, sym->len+1 );
			}
		}
		WritePad( TotalSymbolLength );
	}

	// write the useful dictionary

	if( dict2_size > 0 ) {
		DEBUG(("WriteArMlibFileTable: writing 2. directory\n" ));
		arch.size = dict2_size;     // word round size
		arch.name = "/";
		WriteFileHeader( &arch );

		if( Options.libtype == WL_LTYPE_AR ) {
			WriteLittleEndian32( NumFiles );
			for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
				WriteLittleEndian32( sfile->new_offset );
			}
		}

		WriteLittleEndian32( NumSymbols );
		switch( Options.libtype ) {
		case WL_LTYPE_AR:
			for( i = 0; i < NumSymbols; ++i ) {
				WriteLittleEndian16( SortedSymbols[ i ]->file->index );
			}
			break;
		case WL_LTYPE_MLIB:
			for( i = 0; i < NumSymbols; ++i ) {
				WriteLittleEndian32( SortedSymbols[ i ]->file->index );
			}
			for( i = 0; i < NumSymbols; ++i ) {
				WriteNew( &(SortedSymbols[ i ]->info), 1 );
			}
			break;
		}
		for( i = 0; i < NumSymbols; ++i ) {
			WriteNew( SortedSymbols[ i ]->name, SortedSymbols[ i ]->len + 1 );
		}
		switch( Options.libtype ) {
		case WL_LTYPE_AR:
			WritePad( TotalSymbolLength );
			break;
		case WL_LTYPE_MLIB:
			WritePad( dict2_size );
			break;
		}
	}

	// write the string table

	/* jwlink: for COFF, always write the "longnames" member,
	 * even if it's empty
	 */
	//if( TotalNameLength > 0 ) {
	if( TotalNameLength > 0 || Options.ar_libformat == AR_FMT_COFF ) {
		char    *stringpad;
		int     stringpadlen;

		DEBUG(("WriteArMlibFileTable: writing string table\n" ));
		if( Options.libtype == WL_LTYPE_MLIB ) {
			stringpad    = LIB_LONG_NAME_END_STRING;
			stringpadlen = LIB_LONG_NAME_END_STRING_LEN;
		} else if( Options.ar_libformat == AR_FMT_GNU ) {
			stringpad    = AR_LONG_NAME_END_STRING;
			stringpadlen = AR_LONG_NAME_END_STRING_LEN;
		} else if( Options.ar_libformat == AR_FMT_BSD ) {
			stringpad    = NULL;
			stringpadlen = 0;
		} else {
			stringpad    = "\0";
			stringpadlen = 1;
		}
		arch.size = TotalNameLength;        // real size
		arch.name = "//";
		WriteFileHeader( &arch );
		for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
			if( sfile->name_offset == -1 )
				continue;
			// Always write the "full" filename for AR
			if( Options.libtype == WL_LTYPE_AR && sfile->arch.ffname ) {
				WriteNew( sfile->arch.ffname, sfile->ffname_length );
			} else {
				WriteNew( sfile->arch.name, sfile->name_length );
			}
			WriteNew( stringpad, stringpadlen );
		}
		WritePad( TotalNameLength );
	}

	// write the full filename table

	if( Options.libtype == WL_LTYPE_MLIB ) {
		DEBUG(("WriteArMlibFileTable: writing full filename table\n" ));
		arch.size = TotalFFNameLength;      // real size
		arch.name = "///";
		WriteFileHeader( &arch );
		for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
			WriteNew( sfile->arch.ffname, sfile->ffname_length + 1 );
		}
		WritePad( TotalFFNameLength );
	}

	for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
		char        buff[ AR_NAME_LEN + 1 ];
		bool        append_name;

		append_name = FALSE;
		arch = sfile->arch;
		if( sfile->name_offset == -1 ) {
			if( Options.ar_libformat == AR_FMT_BSD ) {
				// BSD append file name after header and before file image if it is longer then
				//  max.length or it contains space
				if( sfile->name_length > AR_NAME_LEN || strchr( sfile->arch.name, ' ' ) != NULL ) {
					append_name = TRUE;
					arch.size += sfile->name_length;
					strcpy( buff, AR_NAME_CONTINUED_AFTER );
					itoa( sfile->name_length, buff + AR_NAME_CONTINUED_AFTER_LEN, 10 );
					arch.name = buff;
				} else {
					arch.name = sfile->arch.name;
				}
			} else {        // COFF, GNU
				strcpy( buff, sfile->arch.name );
				buff[ sfile->name_length ] = AR_NAME_END_CHAR;
				buff[ sfile->name_length + 1 ] = '\0';
				arch.name = buff;
			}
		} else {
			buff[ 0 ] = '/';
			itoa( sfile->name_offset, buff+1, 10 );
			arch.name = buff;
		}
		WriteFileHeader( &arch );
		if( append_name ) {
			WriteNew( sfile->arch.name, sfile->name_length );
		}
		WriteFile( sfile );
		WritePad( arch.size );
	}
	DEBUG(("WriteArMlibFileTable exit\n"));
}

void WriteFileTable( void )
/*************************/
{
	DEBUG(("WriteFileTable enter\n"));
	if( Options.libtype == WL_LTYPE_NONE && Options.omf_found ) {
		if( Options.coff_found ) {
			Options.libtype = WL_LTYPE_AR;
		} else {
			Options.libtype = WL_LTYPE_OMF;
		}
	}
	if( Options.coff_found && (Options.libtype == WL_LTYPE_NONE || Options.libtype == WL_LTYPE_OMF) ) {
		Options.libtype = WL_LTYPE_AR;
	}
	if( Options.elf_found && (Options.libtype == WL_LTYPE_NONE || Options.libtype == WL_LTYPE_OMF) ) {
		Options.libtype = WL_LTYPE_AR;
	}
	if( Options.libtype == WL_LTYPE_AR || Options.libtype == WL_LTYPE_MLIB ) {
		WriteArMlibFileTable();
	} else {
		WriteOmfFileTable();
	}
}

static int Hash( char *string, unsigned *plen )
/*********************************************/
{
	unsigned long       g;
	unsigned long       h;

	h = 0;
	*plen = 0;
	while( *string != 0 ) {
		h = ( h << 4 ) + *string;
		if( (g = h & 0xf0000000) ) {
			h = h ^ ( g >> 24 );
			h = h ^ g;
		}
		++string;
		++*plen;
	}
	return( h % HASH_SIZE );
}

void AddSym( char *name, symbol_strength strength, unsigned char info )
/*********************************************************************/
{
	sym_entry   *sym,**owner;
	int         hash;
	unsigned    name_len;

	hash = Hash( name, &name_len );
	for( sym = HashTable[ hash ]; sym != NULL; sym = sym->hash ) {
		if( sym->len != name_len )
			continue;
		if( SymbolNameCmp( sym->name, name ) == 0 ) {
			if( strength > sym->strength ) {
				owner = &sym->file->first;
				while( *owner != sym ) {
					owner = &(*owner)->next;
				}
				*owner = sym->next;
				owner = &HashTable[ hash ];
				while( *owner != sym ) {
					owner = &(*owner)->hash;
				}
				*owner = sym->hash;
				MemFreeGlobal( sym );
				break; //db
			} else if( strength == sym->strength ) {
				if( strength == SYM_STRONG ) {
					Warning( ERR_DUPLICATE_SYMBOL, FormSym( name ) );
				}
			}
			return;
		}
	}
	sym = MemAllocGlobal( sizeof( sym_entry ) + name_len );
	sym->len = name_len;
	sym->strength = strength;
	sym->info = info;
	memcpy( sym->name, name, name_len + 1 );
	sym->next = CurrFile->first;
	CurrFile->first = sym;
	sym->file = CurrFile;
	sym->hash = HashTable[ hash ];
	HashTable[ hash ] = sym;
}


#ifdef __DEBUG__
void DumpFileTable( void )
{
	sym_file    *sfile;
	sym_entry   *entry;
	sym_entry   *hash;
	unsigned    len;
	int         hval;
	long        files    = 0L;
	long        symbols  = 0L;

	printf("----------------------------------------------------------\n");
	printf("File Table Dump\n");
	printf("----------------------------------------------------------\n");

	for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
		++files;
		printf( "File: \"%s\"\n", sfile->full_name );
		for( entry = sfile->first; entry; entry = entry->next ) {
			++symbols;

			hval = Hash( entry->name, &len );
			printf( "\t\"%s\" (%d, %u, \"%s\")", entry->name, hval, len,
					(HashTable[ hval ] ? HashTable[ hval ]->name : "(NULL)") );

			for( hash = entry->hash; hash; hash = hash->hash ) {
				printf( " -> \"%s\"", hash->name );
				fflush( stdout );
			}
			printf( "\n" );
		}
	}
	printf( "----------------------------------------------------------\n" );
	printf( "Files         : %ld\n", files );
	printf( "Symbols       : %ld\n", symbols );
	printf( "----------------------------------------------------------\n" );
}


void DumpHashTable( void )
{
	sym_entry   *hash;
	int         i;
	int         length;

	printf( "----------------------------------------------------------\n" );
	printf( "Hash Table Dump\n" );
	printf( "----------------------------------------------------------\n" );

	for( i = 0; i < HASH_SIZE; ++i ) {
		length = 0;

		if( HashTable[ i ] ) {
			for( hash = HashTable[ i ]; hash; hash = hash->next ) {
				++length;
			}
		}

		printf( "Offset %6d: %d\n", i, length );
	}
	printf( "----------------------------------------------------------\n" );
}
#endif // __DEBUG__


bool RemoveObjectSymbols( char *name )
/************************************/
{
	sym_file    *sfile;
	sym_file    *prev_sfile;
	sym_entry   *sym;

	prev_sfile = NULL;
	for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
		if( SymbolNameCmp( sfile->full_name, name ) == 0 ) {
			if( prev_sfile != NULL ) {    /* Not deleting from head of list */
				prev_sfile->next = sfile->next;

				if( FileTable.add_to == &sfile->next ) { /* Last node in list */
					FileTable.add_to = &prev_sfile->next;
				}
			} else {
				if( FileTable.add_to == &sfile->next ) { /* Only node in list */
					FileTable.add_to = &FileTable.first;
					FileTable.first = NULL;
				} else {  /* First node in the list */
					FileTable.first = sfile->next;
				}
			}

			for( sym = sfile->first; sym != NULL; sym = sym->next ) {
				RemoveFromHashTable( sym );
			}

			FiniSymFile( sfile );
			Options.modified = TRUE;
			return( TRUE );
		}
		prev_sfile = sfile;
	}
	return( FALSE );
}

void AddObjectSymbols( arch_header *arch, libfile io, long offset )
/*****************************************************************/
{
	obj_file    *ofile;
	file_type   obj_type;

	ofile = OpenLibFile( arch->name, io );
	if( ofile->orl != NULL ) {
		if( ORLFileGetFormat( ofile->orl ) == ORL_COFF ) {
			if( Options.libtype == WL_LTYPE_MLIB ) {
				FatalError( ERR_NOT_LIB, "COFF", LibFormat() );
			}
			Options.coff_found = TRUE;
			obj_type = WL_FTYPE_COFF;
		} else {
			if( Options.omf_found ) {
				FatalError( ERR_MIXED_OBJ, "ELF", "OMF" );
			}
			Options.elf_found = TRUE;
			obj_type = WL_FTYPE_ELF;
		}
	} else {
		if( Options.libtype == WL_LTYPE_MLIB ) {
			FatalError( ERR_NOT_LIB, "OMF", LibFormat() );
		}
		if( Options.elf_found ) {
			FatalError( ERR_MIXED_OBJ, "ELF", "OMF" );
		}
		Options.omf_found = TRUE;
		obj_type = WL_FTYPE_OMF;
	}
	NewSymFile( arch );
	CurrFile->obj_type = obj_type;
	CurrFile->inlib_offset = offset;
	CurrFile->inlib = FindInLib( io );
	ObjWalkSymList( ofile, CurrFile );
	CloseLibFile( ofile );
}

void OmfMKImport( arch_header *arch, importType type,
				  long ordinal, char *DLLname, char *symName,
				  char *exportedName, processor_type processor )
{
	if( Options.elf_found ) {
		FatalError( ERR_MIXED_OBJ, "ELF", "OMF" );
	}
	Options.omf_found = TRUE;
	NewSymFile( arch );
	CurrFile->obj_type = WL_FTYPE_OMF;
	CurrFile->import = MemAllocGlobal( sizeof( import_sym ) );
	CurrFile->import->DLLName = DupStrGlobal( DLLname );
	CurrFile->import->u.sym.ordinal = ordinal;
	if( symName != NULL ) {
		CurrFile->import->u.sym.symName = DupStrGlobal( symName );
	} else {
		CurrFile->import->u.sym.symName = NULL;
	}
	if( exportedName != NULL ) {
		CurrFile->import->u.sym.exportedName = DupStrGlobal( exportedName );
	} else {
		CurrFile->import->u.sym.exportedName = NULL;
	}
	CurrFile->import->type = type;
	CurrFile->import->processor = processor;
	CurrFile->arch.size = OmfImportSize( CurrFile->import );
	AddSym( symName, SYM_STRONG, 0 );
}

void CoffMKImport( arch_header *arch, importType type,
				   long ordinal, char *DLLname, char *symName, 
				   char *exportedName, processor_type processor )
{
	if( Options.elf_found ) {
		FatalError( ERR_MIXED_OBJ, "ELF", "COFF" );
	}
	Options.coff_found = TRUE;
	NewSymFile( arch );
	CurrFile->obj_type = WL_FTYPE_COFF;
	CurrFile->import = MemAllocGlobal( sizeof( import_sym ) );
	CurrFile->import->type = type;
	CurrFile->import->u.sym.ordinal = ordinal;
	CurrFile->import->DLLName = DupStrGlobal( DLLname );
	if( symName != NULL ) {
		CurrFile->import->u.sym.symName = DupStrGlobal( symName );
	} else {
		CurrFile->import->u.sym.symName = NULL;
	}
	if( exportedName != NULL ) {
		CurrFile->import->u.sym.exportedName = DupStrGlobal( exportedName );
	} else {
		CurrFile->import->u.sym.exportedName = NULL;
	}
	CurrFile->import->processor = processor;
	CurrFile->arch.size = CoffImportSize( CurrFile->import );
	switch( type ) {
	case IMPORT_DESCRIPTOR:
	case NULL_IMPORT_DESCRIPTOR:
	case NULL_THUNK_DATA:
		AddSym( symName, SYM_WEAK, 0 );
		break;
	default:
		AddSym( symName, SYM_STRONG, 0 );
		break;
	}
}

void ElfMKImport( arch_header *arch, importType type, long export_size,
				  char *DLLname, char *strings, Elf32_Export *export_table,
				  Elf32_Sym *sym_table, processor_type processor )
{
	int                 i;
	elf_import_sym      **temp;
	elf_import_sym      *imp_sym;

	if( Options.coff_found ) {
		FatalError( ERR_MIXED_OBJ, "ELF", "COFF" );
	}
	if( Options.omf_found ) {
		FatalError( ERR_MIXED_OBJ, "ELF", "OMF" );
	}
	Options.elf_found = TRUE;
	NewSymFile( arch );
	CurrFile->obj_type = WL_FTYPE_ELF;
	CurrFile->import = MemAllocGlobal( sizeof( import_sym ) );
	CurrFile->import->type = type;
	CurrFile->import->DLLName = DupStrGlobal( DLLname );
	CurrFile->import->u.elf.numsyms = 0;
	temp = &(CurrFile->import->u.elf.symlist);

	for( i = 0; i < export_size; i++ ) {
		if( export_table[ i ].exp_symbol ) {
			imp_sym = MemAllocGlobal( sizeof( elf_import_sym ) );
			imp_sym->name = DupStrGlobal( &(strings[ sym_table[ export_table[ i ].exp_symbol ].st_name ]) );
			imp_sym->len = strlen( imp_sym->name );
			imp_sym->ordinal = export_table[ i ].exp_ordinal;
			if( type == ELF ) {
				AddSym( imp_sym->name, SYM_STRONG, ELF_IMPORT_SYM_INFO );
			}

			CurrFile->import->u.elf.numsyms ++;

			*temp = imp_sym;
			temp = &(imp_sym->next);
		}
	}
	*temp = NULL;
	CurrFile->import->processor = processor;
	CurrFile->arch.size = ElfImportSize( CurrFile->import );
}

#define MAX_MESSAGE_LEN 511
static char             listMsg[ MAX_MESSAGE_LEN + 1 ];
static unsigned         msgLength = 0;

static void listPrint( FILE *fp, char *str, ... )
{
	va_list             arglist;
	va_start( arglist, str );
	msgLength += _vsnprintf( listMsg + msgLength, MAX_MESSAGE_LEN - msgLength, str, arglist );
	va_end( arglist );
}

static void listNewLine( FILE *fp )
{
	if( fp ) {
		fprintf( fp, listMsg );
		fputc( '\n', fp );
	} else {
		Message( listMsg );
	}
	msgLength = 0;
	listMsg[ 0 ] = ' ';
	listMsg[ 1 ] = '\0';
}

#define LINE_WIDTH 79
#define OFF_COLUMN 40

static void fpadch( FILE *fp, char ch, int len )
{
	if( len <= 0 ) {
		return;
	}
	len = min( MAX_MESSAGE_LEN - msgLength, len );
	memset( listMsg + msgLength, ch, len );
	msgLength += len;
	listMsg[ msgLength ] = '\0';
}

static void printVerboseTableEntry( arch_header *arch )
{
	char        member_mode[ 11 ];
	char        date[ 128 ];
	time_t      t;

	member_mode[ 10 ] = '\0';
	member_mode[ 9 ] = ' ';
	if( arch->mode & S_IRUSR ) {
		member_mode[ 0 ] = 'r';
	} else {
		member_mode[ 0 ] = '-';
	}
	if( arch->mode & S_IWUSR ) {
		member_mode[ 1 ] = 'w';
	} else {
		member_mode[ 1 ] = '-';
	}
	if( !( arch->mode & S_IXUSR ) && (arch->mode & S_ISUID ) ) {
		member_mode[ 2 ] = 'S';
	} else if( ( arch->mode & S_IXUSR ) && ( arch->mode & S_ISUID ) ) {
		member_mode[ 2 ] = 's';
	} else if( arch->mode & S_IXUSR ) {
		member_mode[ 2 ] = 'x';
	} else {
		member_mode[ 2 ] = '-';
	}
	if( arch->mode & S_IRGRP ) {
		member_mode[ 3 ] = 'r';
	} else {
		member_mode[ 3 ] = '-';
	}
	if( arch->mode & S_IWGRP ) {
		member_mode[ 4 ] = 'w';
	} else {
		member_mode[ 4 ] = '-';
	}
	if( !( arch->mode & S_IXGRP ) && (arch->mode & S_ISGID ) ) {
		member_mode[ 5 ] = 'S';
	} else if( ( arch->mode & S_IXGRP ) && ( arch->mode & S_ISGID ) ) {
		member_mode[ 5 ] = 's';
	} else if( arch->mode & S_IXGRP ) {
		member_mode[ 5 ] = 'x';
	} else {
		member_mode[ 5 ] = '-';
	}
	if( arch->mode & S_IROTH ) {
		member_mode[ 6 ] = 'r';
	} else {
		member_mode[ 6 ] = '-';
	}
	if( arch->mode & S_IWOTH ) {
		member_mode[ 7 ] = 'w';
	} else {
		member_mode[ 7 ] = '-';
	}
	if( arch->mode & S_IXOTH ) {
		member_mode[ 8 ] = 'x';
	} else {
		member_mode[ 8 ] = '-';
	}
	t = (time_t) arch->date;
	strftime( date, 127, "%b %d %H:%M %Y", localtime( &t ) );
	Message( "%s %u/%u %u %s %s", member_mode, arch->uid, arch->gid, arch->size,
		date, MakeFName( arch->name ) );
}


void ListContents( void )
/******************************/
{
	sym_file    *sfile;
	lib_cmd     *cmd;

	if( Options.ar ) {
		if( CmdList ) {
			for( cmd = CmdList; cmd != NULL; cmd = cmd->next ) {
				if( cmd->ops & OP_FOUND ) {
					if( Options.verbose ) {
						for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
							if( SameName( sfile->arch.name, cmd->name ) ) {
								if( Options.terse_listing ) {
									Message( sfile->arch.name );
								} else {
									printVerboseTableEntry( &( sfile->arch ) );
								}
								break;
							}
						}
					} else {
						Message( MakeFName( cmd->name ) );
					}
				}
			}
		} else {
			if( Options.verbose ) {
				for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
					printVerboseTableEntry( & ( sfile->arch ) );
				}
			} else {
				for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
					Message( "%s", MakeFName( sfile->arch.name ) );
				}
			}
		}
	} else {
		sym_entry       *sym;
		int             i;
		FILE            *fp;
		char            *name;
		unsigned        name_len;

		if( Options.terse_listing ) {
			SortSymbols();
			for( i = 0; i < NumSymbols; ++i ) {
				sym = SortedSymbols[ i ];
				name = FormSym( sym->name );
				name_len = strlen( name );
				Message(name);
			}
			return;
		}

		if( Options.list_file == NULL ) {
			Options.list_file = DupStr( MakeListName() );
		}
		if( Options.list_file[ 0 ] != 0 ) {
			fp = fopen( Options.list_file, "w" );
			if( fp == NULL ) {
				FatalError( ERR_CANT_OPEN, Options.list_file, strerror( errno ) );
			}
		} else {
			fp = NULL;
		}
		SortSymbols();

		for( i = 0; i < NumSymbols; ++i ) {
			sym = SortedSymbols[ i ];
			name = FormSym( sym->name );
			name_len = strlen( name );
			listPrint( fp, "%s..", name );
			fpadch( fp, '.', LINE_WIDTH - 2 - name_len - sym->file->name_length );
			listPrint( fp, "%s", sym->file->arch.name );
			listNewLine( fp );
		}

		listNewLine( fp );

		for( sfile = FileTable.first; sfile != NULL; sfile = sfile->next ) {
			if( sfile->arch.ffname ) {
				listPrint( fp, "%s ", sfile->arch.ffname );
			} else {
				listPrint( fp, "%s ", sfile->arch.name );
			}
			fpadch( fp, ' ', OFF_COLUMN - 1 - sfile->name_length - 16 );
			listPrint( fp, "Offset=%8.8xH", sfile->inlib_offset );
			listNewLine( fp );
			for( sym = sfile->first; sym != NULL; sym = sym->next ) {
				listPrint( fp, "    %s", FormSym( sym->name ) );
				listNewLine( fp );
			}
			listNewLine( fp );
		}

		if( fp ) {
			fclose( fp );
		}
	}
}
